package com.kaitian.demo.demo_boot_starter.runner;

import com.kaitian.demo.demo_boot_starter.service.IDemoService;
import com.kaitian.demo.demo_boot_starter.service.IRedisService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author ：xietian
 * @version ：V1.0
 * @program ：demo
 * @date ：Created in 2022/4/8 15:57
 * @description ：超卖的测试
 */
@Slf4j
@Component
public class OverSaleRunner implements ApplicationRunner {

    private static final Random random = new Random();

    /**
     * 用于高并发实时任务的线程池
     * 线程池不限制线程上限,且默认持有20个核心线程,但非核心线程仅存活较短时间将销毁
     * 用于即时异步的短期任务
     * */
    private ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor() {
        {
            this.setThreadNamePrefix("MS-");
            this.setCorePoolSize(50);
            this.setMaxPoolSize(200);
            this.setQueueCapacity(10000);
            this.initialize();
        }
    };

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private IRedisService redisService;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        log.info("项目启动后运行：{}", this.getClass().getSimpleName());
        // 【卒】没有事务的抢购操作
//        testDemo0("over_sale:demo1");
        // 【卒】没有事务，百分百超卖
//        testDemo1("over_sale:demo2");
        // 【活】方法使用sync关键字，解决了超卖，这样用同步影响范围比较大
//        testDemo2("over_sale:demo3");
        // 【可抢救】部分代码使用sync关键字，无法解决超卖
        // 使用synchronized(this)就能保证代码块的互斥访问
        testDemo3("over_sale:demo4");
        // 【卒】双重校验锁，未解决超卖问题
//        testDemo4("over_sale:demo5");
        // 【活】Redis Set NX 分布式锁，可以解决超卖
//        testDemo5("over_sale:demo6");
        // 【活】Redis 事务和watch乐观锁，可以解决超卖问题
//        testDemo6("over_sale:demo7");

        log.info("测试调用方法完成！");
    }

    @SneakyThrows
    private void testDemo0(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct0(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (futureList.size() >= 200) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo0执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo0执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }

    @SneakyThrows
    private void testDemo1(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct1(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (futureList.size() >= 200) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo1执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo1执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }

    @SneakyThrows
    private void testDemo2(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct2(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (futureList.size() >= 200) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo2执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo2执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }

    @SneakyThrows
    private void testDemo3(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct3(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (futureList.size() >= 200) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo3执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo3执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }

    @SneakyThrows
    private void testDemo4(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct4(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (futureList.size() >= 200) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo4执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo4执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }

    @SneakyThrows
    private void testDemo5(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct5(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (count >= 100) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo5执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo5执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }

    @SneakyThrows
    private void testDemo6(String key) {

        // 1. 初始商品库存信息
        redisService.initProduct(key, 100);

        // 2. 开始抢购
        List<Future<Integer>> futureList = new ArrayList<>();
        Integer count = 0;
        while(true) {
            Future<Integer> submit = taskExecutor.submit(() -> {
                Integer num = random.nextInt(3) + 1;
                Integer buyNum = redisService.buyProduct6(key, num);
                return null == buyNum ? num : buyNum;
            });

            futureList.add(submit);
            count += submit.get();

            if (count >= 100) {
                // 睡3秒在获取
                Thread.sleep(3000L);
                System.out.println("testDemo6执行结束！产生调用的线程数：" + futureList.size());
                System.out.println("testDemo6执行结束！最终数据：" + redisTemplate.opsForValue().get(key) + " 成功秒杀：" + count);
                break;
            }
        }
    }
}